<?php
/**
 * Game_Battle_Battle
 *
 * Класс Game_Battle_Battle отвечает за сражения
 *
 * @author Дмитрий Кузьма <kouzma.d@gmail.com>
 * @version 1.0
 */
class Game_Battle_Battle extends Game_Battle_Move {
	
	

	
	protected static $_availableBattleTypes = array('death_attack','raid_attack');
	protected $_arrDefenderUnits = array();
	
	
	// private $_attackPoints_1 = 0;
	// private $_attackPoints_2 = 0;
	
	protected $_defenderDiedPart;
	protected $_attackerDiedPart;
	
	protected $_objResourcesToStolen;
	
	const BASE_PLACE_DEFENSE = 10;
	const MIN_DEFENSE_AGAINST_1 = 41.4999;
	
	/**
	 * Создает новый объект для расчета сражения
	 *
	 * @param string $battleType - тип сражения
	 * @param Game_Place $objPlaceGoal - место, в котором происходит сражение
	 * @param Game_Unit_UnitsGroup $objSentGroup - Группа атакующих войск
	 * @param bool $isAutoCalculate - флаг, производить ли посдечт сразу
	 * 
	 */
	public function __construct($battleType, Game_Place $objPlaceGoal, Game_Unit_UnitsGroup $objSentGroup, $isAutoCalculate = true) {
		parent::__construct($objPlaceGoal, $objSentGroup);
		$this->setBattleType($battleType);
		if ($isAutoCalculate) $this->calculate();
	}
	
	/**
	 * Сохраняет результаты в битвы в БД
	 */
	private function _saveBattleResults() {
		// Сохраняем объект битвы
		$battleId = $this->_saveBattleEssence();
		
		// Сохраняем "ссылки на отчеты" для каждого из пользователей
		$this->_saveBattleUsersInfo($battleId);
		
		// Сохраняем информацию по битве
		$this->_saveBattleUnitsInfo($battleId);
		
		// Записываем, сколько осталось нападающих и куда они движутся
		$this->_saveAttackerTroopsDataNew();
		
		// Записываем, сколько осталось защитников
		$this->_saveDefenderTroopsDataNew();
		
		
	}
	

	
	/**
	 * Сохраняет информацию о войсках атакующего
	 */
	private function _saveAttackerTroopsDataNew() {
		$arrAliveTroops = array();
		$stmtUpdateRows = "";
		$attackerPlaceId = $this->_objSentGroup->getField('ID_place_owner');
		foreach ($this->_arrSentUnits as $arrAttackerUnit) {
			$aliveAmount = $arrAttackerUnit['amount'] - $arrAttackerUnit['amount_died'];
			if ($aliveAmount) $arrAliveTroops[$arrAttackerUnit['ID_unit']] = $aliveAmount;
			if ($arrAttackerUnit['amount_died']) {
				$stmtUpdateRows .= "
					WHEN `ID_unit` = '" . $arrAttackerUnit['ID_unit'] . "'
					THEN `amount` - " .  $arrAttackerUnit['amount_died'] . "
				";
			}
		}
		
		if (!empty($stmtUpdateRows)) {
			$objDb = Service_Database::instance();
			$stmtUpdate ="
				UPDATE `place_unit_amount`
				SET `amount` = 
				CASE 
					" . $stmtUpdateRows . "
				ELSE `amount` 
				END
				WHERE `ID_place` = '" . $attackerPlaceId ."' AND `ID_place_owner` = '" . $attackerPlaceId ."'
			";
			$objDb->query($stmtUpdate);
			$this->_objPlaceSender->calculateAndUpdateDynamicConsumption();
		}
		
		// Если есть выжившие, их нужно отправить обратно
		if ($arrAliveTroops) {

			
			
			$objDb = Service_Database::instance();
			$speed = Game_Unit::getSpeedOfUnitsGroup(Service_ArrayConverter::getColumn($this->_arrSentUnits, 'ID_unit'));
			$objCoordinates = new Game_Coordinates($this->_objPlaceSender);
			$moveDeltaTime = $objCoordinates->getMoveTime($speed, new Game_Coordinates($this->_objPlaceGoal));
			
			$objUnitsGroup = new Game_Unit_UnitsGroup();
			$objUnitsGroup->setCoordinates($objCoordinates);
			$objUnitsGroup->setField('ID_place_owner', $attackerPlaceId);
			$objUnitsGroup->setField('move_type', 'reinforcement');
			$objUnitsGroup->save();
			$objUnitsGroup->saveTroopsInfo($arrAliveTroops);
			
			// Если это набег или атака, то возможен грабеж
			if (in_array($this->_battleType, self::$_availableBattleTypes)) {
				$objResources = $this->_objPlaceGoal->getResourcesCouldBeStolen();
				$arrUnitsInfo = Game_Unit::getAllUnitsInfo();
				$canRobSum = 0;
				// TODO - сюда пишем сохранение данных о грабеже
				foreach ($arrAliveTroops as $unitId => $unitAmount) {
					$baseUnitRob = $arrUnitsInfo[$unitId]['capacity'];
					$canRobSum += $baseUnitRob * $unitAmount;
				}
				$this->_objResourcesToStolen = $objResources->getResourcesByLimit($canRobSum);
				Game_Unit_GroupResource::create($objUnitsGroup->id, $this->_objResourcesToStolen);
				$this->_objPlaceGoal->objResourcesWithProduction->decrease($this->_objResourcesToStolen);
			}
			
			$stmt = "SELECT `event_time` FROM `game_event` WHERE `ID_essence` = '" . $this->_objSentGroup->id . "' AND `event_type` = 'units_move'";
			$attackTime = $objDb->selectSimpleField($stmt);
			$troopsReturnTime = Service_Date::timeWithDelta($attackTime, $moveDeltaTime);
			$objGameEvent = new Game_Event(array(
					'ID_place' => $attackerPlaceId,
					'ID_user' => $this->_objPlaceSender->getField('ID_user'),
					'event_type' => 'units_move',
					'ID_essence' => $objUnitsGroup->id,
					'amount' => 1,
					'event_time' => $troopsReturnTime,
					'event_time_additional_end' => $troopsReturnTime
			));
			

			$objGameEvent->save();
		}
		$this->_objSentGroup->delete();
	}
	
	/**
	 * Записываем в БД новую информацию о войсках защищающегося
	 */
	private function _saveDefenderTroopsDataNew() {
		$stmtUpdateRows = "";
		$stmtDeleteRows = "";
		foreach ($this->_arrDefenderUnits as $arrDefenderUnit) {
			// Производим действия с теми юнитами, где есть потери
			if (isset($arrDefenderUnit['amount_died']) && $arrDefenderUnit['amount_died']) {
				// Готовим к апдейту те юниты, где потери не окончательные
				if (($arrDefenderUnit['amount'] - $arrDefenderUnit['amount_died']) > 0) {
					$stmtUpdateRows .= " 
						WHEN 
							`ID_place_owner` = '" . $arrDefenderUnit['ID_place_owner'] . "' AND 
							`ID_unit` = '" . $arrDefenderUnit['ID_unit'] . "'
						THEN '" . ($arrDefenderUnit['amount'] - $arrDefenderUnit['amount_died']) . "'
					";
				}
				else { // юнит к удалению из БД
					if (!empty($stmtDeleteRows)) $stmtDeleteRows .= ' OR ';
					$stmtDeleteRows .= " (`ID_place_owner` = '" . $arrDefenderUnit['ID_place_owner'] . "' AND `ID_unit` = '" . $arrDefenderUnit['ID_unit'] . "')";
				}
			}
		}

		if (!empty($stmtUpdateRows) || !empty($stmtDeleteRows) ) {

			$objDb = Service_Database::instance();
					if (!empty($stmtUpdateRows)) {
				$stmtUpdate ="
					UPDATE `place_unit_amount`
					SET `amount` = 
					CASE
						" . $stmtUpdateRows . "
					ELSE `amount` 
					END
					WHERE `ID_place` = '" . $arrDefenderUnit['ID_place'] ."'
				";
				$objDb->query($stmtUpdate);
			}
			if (!empty($stmtDeleteRows)) {
				$stmtDelete ="
					DELETE FROM `place_unit_amount`
					WHERE `ID_place` = '" . $arrDefenderUnit['ID_place'] ."' AND (" . $stmtDeleteRows . ")
				";
				$objDb->query($stmtDelete);
			}
			$this->_objPlaceGoal->calculateAndUpdateDynamicConsumption();
		}
		elseif (!empty($this->_objResourcesToStolen)) $this->_objPlaceGoal->saveResourcesData(); // Если войсковых потерь нет, но есть потери в краже, то сохраняем инфу по краже

	}
	/**
	 * Сохраняет информацию о пользователях, участвоваших в битве
	 *
	 * @param int $battleId - идентификатор битвы для сохранения
	 */
	protected function _saveBattleUsersInfo($battleId) {
		parent::_saveBattleUsersInfo($battleId);
		
		$stmt = "SELECT `place`.`ID_user`
			FROM `place_unit_amount`
				INNER JOIN `place` ON `place`.`ID_place` = `place_unit_amount`.`ID_place_owner`
			WHERE
				`place_unit_amount`.`ID_place` = '" . $this->_objPlaceGoal->id . "' AND
				`place`.`ID_user` != '" . $this->_objUserSender->id . "' AND
				`place`.`ID_user` != '" . $this->_objUserGoal->id . "'
			GROUP BY `ID_user`
		";
		$objDb = Service_Database::instance();
		$arrUsers = $objDb->selectSimpleArray($stmt);
		foreach ($arrUsers as $userId) {
			$objBattleUser = new Game_Battle_User($userId, $battleId, 'defend');
			$objBattleUser->save();
		}
	}
	
	/**
	 * Сохраняет информацию о прошедшей битве
	 * 
	 * @param int $battleId - идентификатор битвы для сохранения
	 */
	protected function _saveBattleUnitsInfo($battleId) {
		// Сохраянем данные об атакующем
		$stmtInsertStart = $this->_getInsertIntoBattleUnitStart();
		$stmtInsertValues = $this->_getSentUnitsToBattleUnitInsertValues($battleId);
		foreach ($this->_arrDefenderUnits as $arrDefenderUnit) {
			$stmtInsertValues .= ", (
				'" . $battleId . "', '" . $arrDefenderUnit['ID_unit'] . "', '" . $arrDefenderUnit['ID_place_owner'] . "',
				'" . ($arrDefenderUnit['ID_place_owner'] == $arrDefenderUnit['ID_place'] ? 'own_defend' : 'defend') . "', '" . $arrDefenderUnit['amount'] . "', '" . (isset($arrDefenderUnit['amount_died']) ? $arrDefenderUnit['amount_died'] : 0) . "'
			)";
		} 
		$objDb = Service_Database::instance();
		$arrUsers = $objDb->query($stmtInsertStart . $stmtInsertValues);
	}
	

	
	/**
	 * Задает тип сражения
	 * 
	 * @param string $battleType - тип сражения
	 */
	public function setBattleType($battleType) {
		if (!in_array($battleType, static::$_availableBattleTypes)) throw new Exception_Game_Programming(Service_Translate::getText('Неверно указан тип сражения'));
		$this->_battleType = $battleType;
	}
	
	/**
	 * 
	 * @param Game_Place $objPlaceGoal
	 */
	public function setPlaceGoal(Game_Place $objPlaceGoal) {
		parent::setPlaceGoal($objPlaceGoal);
		$this->_setDefenderTroops();
	}

	
	/**
	 * Заполняет информацию о войсках обороы
	 */
	private function _setDefenderTroops() {
		$objDb = Service_Database::instance();
		$defenderPlaceId = $this->_objPlaceGoal->id;
		$stmt = "
			SELECT * FROM `place_unit_amount` 
			WHERE `ID_place` = '" . $defenderPlaceId . "'
		";
		$arrUnits = $objDb->select($stmt);
		if (!$arrUnits) {
			$this->_arrDefenderUnitsGroups = array();
			return false;
		}
		
		// Проверяем, есть ли в месте собственные юниты
		$hasOwnUnits = false;
		foreach ($arrUnits as $arrUnitInfo) {
			if ($arrUnitInfo['ID_place_owner'] == $defenderPlaceId) {
				$hasOwnUnits = true;
				break;
			}
		}
		
		// Если есть собственные юниты, то по необходимости вычитаем те, что в пути
		if ($hasOwnUnits) {
			$stmt = "
				SELECT `units_in_group`.`ID_unit`, `units_in_group`.`unit_level`, SUM(`units_in_group`.`amount`) AS `amount` FROM `units_group`
				INNER JOIN `units_in_group` USING(`ID_units_group`)
				WHERE `ID_place_owner` = '" . $defenderPlaceId . "'
				GROUP BY `ID_unit`
			";
			$arrOwnUnitsInWay = $objDb->select($stmt);
			foreach ($arrOwnUnitsInWay as $arrOwnUnitInWayInfo) {
				foreach ($arrUnits as $key => $arrUnitInfo) {
					if ($arrUnitInfo['ID_place_owner'] == $defenderPlaceId && $arrUnitInfo['ID_unit'] == $arrOwnUnitInWayInfo['ID_unit']) {
						$arrUnits[$key]['amount'] -= $arrOwnUnitInWayInfo['amount'];
						break;
					}
				}
			}
		}
		$this->_arrDefenderUnits = $arrUnits;
	}
	
	/**
	 * Задает очки в сражении
	 */
	protected function _setBattlePoints() {
		// Подсчитываем очки атаки
		$arrAttackPoints = Game_Unit::calculateUnitsGroupAttack($this->_objSentGroup);
		$this->_attackPoints =  $arrAttackPoints[1] + $arrAttackPoints[2];
		$arrAttackProportion = array(
				1 => ($arrAttackPoints[1] ? ($arrAttackPoints[1] / $this->_attackPoints) : 0),
				2 => ($arrAttackPoints[2] ? ($arrAttackPoints[1] / $this->_attackPoints) : 0),
		);
		
		// Подсчитываем очки защиты
		$arrDefensePoints = Game_Unit::calculateUnitsDefense($this->_arrDefenderUnits);
		$this->_defensePoints = $arrDefensePoints[1] * $arrAttackProportion[1] + $arrDefensePoints[2] * $arrAttackProportion[2];
		$this->_defensePoints += self::BASE_PLACE_DEFENSE; // Добавляем базовую защиту места
		
		// Добавляем коэффициент морали
		$moraleCoeff = $this->_getMoraleCoeff($this->_attackPoints, $this->_defensePoints);
		$this->_defensePoints *= $moraleCoeff;
		
		// Проверка на атаку одним юнитом
		if ($this->_defensePoints < self::MIN_DEFENSE_AGAINST_1) {
			if (sizeof($this->_arrSentUnits) == 1) {
				if ($this->_arrSentUnits[0]['amount'] == 1) {
					$this->_defensePoints =self::MIN_DEFENSE_AGAINST_1;
				}
			}
		}
	}
	

	/**
	 * Вычисляет потери каждой из сторон
	 */
	public function calculate() {
		$this->_setBattlePoints();
		
		$winPoints = max($this->_attackPoints, $this->_defensePoints);
		$losePoints = min($this->_attackPoints, $this->_defensePoints);
		
		
		
		$winerPowCoeff = $this->_getWinnerPowCoeff();
		$winDeath = pow(
			($losePoints / $winPoints),
			$winerPowCoeff 
		);
		$loseDeath = 1;
		if ($this->_battleType == 'raid_attack') {
			$winDeath = $this->_getWinRaidDeath($winDeath);
			$loseDeath = 1 - $winDeath;
		}

		$this->_defenderDiedPart = ($this->_defensePoints >= $this->_attackPoints) ? $winDeath : $loseDeath;
		$this->_attackerDiedPart = ($this->_defensePoints >= $this->_attackPoints) ? $loseDeath : $winDeath;
		$this->_setDies();
		$this->_saveBattleResults();
	}
	
	/**
	 * Определяет кол-во юнитов, задействованных в битве
	 */
	private function _getUnitsInBattle() {
		$unitsAmount = 0;
		
		// Считаем защитные юниты
		foreach ($this->_arrDefenderUnits as $arrUnitData) {
			$unitsAmount += $arrUnitData['amount'];
		}
		
		// Считаем атакующие юниты
		$arrAttackerUnits = $this->_objSentGroup->getUnitsInGroup();
		foreach ($arrAttackerUnits as $arrUnitData) {
			$unitsAmount += $arrUnitData['amount'];
		}
		
		return $unitsAmount;
	}
	
	/**
	 * Вычисляет коэффициент смертности конкретного юнита
	 */
	protected function _getWinnerPowCoeff() {
		$unitsInBattle = $this->_getUnitsInBattle();
		$winerPowCoeff = 2 * (
			1.8592 - pow($unitsInBattle, 0.015)
		);
		$winerPowCoeff = max(1.2578, $winerPowCoeff); 
		$winerPowCoeff = min(1.5, $winerPowCoeff);
		return $winerPowCoeff;
	}
	
	/**
	 * Получает потери победителя при набеги по потерям победителя при смертельной атаке 
	 * 
	 * @param numeric $totalDeath - потери победителя при смертельной атаке
	 */
	private function _getWinRaidDeath($totalDeath) {
		return (1 * $totalDeath) / (1 + $totalDeath);
	}
	
	/**
	 * Получает дополнительны коэффициент морали защитника
	 * 
	 * @param int $attackPoints
	 * @param int $defensePoints
	 * @return number|mixed
	 */
	protected function _getMoraleCoeff($attackPoints, $defensePoints) {
		// Определяем население защитника
		$populationDefender = $this->_objUserGoal->getField('population');
		
		// Определяем население нападающего
		$populationAttacker = $this->_objUserSender->getField('population');
		if ($populationAttacker <= $populationDefender) return 1;

		$defenseMoraleCoeff = pow(
				($populationAttacker / $populationDefender),
				0.2 * min(
					1,
					$attackPoints / $defensePoints
				)
		);
		return min($defenseMoraleCoeff, 1.5);
		
	}
	
	/**
	 * Задает смерти
	 */
	protected function _setDies() {
		foreach ($this->_arrSentUnits as &$arrAttackerUnit) {
			$arrAttackerUnit['amount_died'] = round($arrAttackerUnit['amount'] * $this->_attackerDiedPart);
		}
		foreach ($this->_arrDefenderUnits as &$arrDefenderUnit) {
			$arrDefenderUnit['amount_died'] = round($arrDefenderUnit['amount'] * $this->_defenderDiedPart);
		}
	}
	
	
}
?>